using System;
using System.Data;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using NetFramework.Configuration;

namespace NetFramework.DataAccess
{
	/// <summary>
	/// DataProviderFactory returns DataProvider objects.
	/// Maintains/refreshes internal cache of DataProviders.
	/// Config info retrieved from Framework.DataAccess.dll.config
	/// </summary>
	internal class DataProviderFactory
	{
		private static Dictionary<string, DataProvider> _dataProviders = null;

		static DataProviderFactory()
		{
			LoadCache();
		}

		private static void LoadCache()
		{
			try
			{
                dataAccessSettings settings = ConfigManager.XmlConfig.GetConfig<dataAccessSettings>();

                _dataProviders = new Dictionary<string, DataProvider>();

                if (settings.dataProviders != null)
                {
                    LoadDataProvide(settings);
                }
                else
                {
                    _dataProviders.Add("SqlClient", new SqlDataProvider());
                }
			}
			catch(Exception e)
			{
				throw new DataAccessException("Error loading cache from config.", e);
			}
		}

        private static void LoadDataProvide(dataAccessSettings settings)
        {
            foreach (dataProvider dp in settings.dataProviders)
            {
                Type connectionType = Type.GetType(dp.connectionType);
                if (connectionType == null)
                    throw new DataAccessException("Could not load connectionType for typeName " + dp.connectionType + " for provider " + dp.name);

                Type commandType = Type.GetType(dp.commandType);
                if (commandType == null)
                    throw new DataAccessException("Could not load commandType for typeName " + dp.commandType + " for provider " + dp.name);

                Type parameterType = Type.GetType(dp.parameterType);
                if (parameterType == null)
                    throw new DataAccessException("Could not load parameterType for typeName " + dp.parameterType + " for provider " + dp.name);

                PropertyInfo parameterDbTypeProperty =
                    parameterType.GetProperty(
                    dp.parameterDbTypeProperty,
                    BindingFlags.Instance | BindingFlags.Public);
                if (parameterDbTypeProperty == null)
                    throw new DataAccessException("Could not load parameterDbTypeProperty for typeName " + dp.parameterDbTypeProperty + " for provider " + dp.name);

                Type parameterDbType = Type.GetType(dp.parameterDbType);
                if (parameterDbType == null)
                    throw new DataAccessException("Could not load parameterDbType for typeName " + dp.parameterDbType + " for provider " + dp.name);

                Type dataAdapterType = null;
                if (dp.dataAdapterType != null && dp.dataAdapterType != string.Empty)
                {
                    dataAdapterType = Type.GetType(dp.dataAdapterType);
                    if (dataAdapterType == null)
                        throw new DataAccessException("Could not load dataAdapterType for typeName " + dp.dataAdapterType + " for provider " + dp.name);
                }

                Type commandBuilderType = null;
                if (dp.commandBuilderType != null && dp.commandBuilderType != string.Empty)
                {
                    commandBuilderType = Type.GetType(dp.commandBuilderType);
                    if (commandBuilderType == null)
                        throw new DataAccessException("Could not load commandBuilderType for typeName " + dp.commandBuilderType + " for provider " + dp.name);
                }

                if (dp.parameterNamePrefix == null)
                    dp.parameterNamePrefix = string.Empty;

                _dataProviders.Add(dp.name,
                    new DataProvider(
                    dp.name, connectionType, commandType,
                    parameterType, parameterDbType, parameterDbTypeProperty,
                    dataAdapterType, commandBuilderType, dp.parameterNamePrefix));

            }
        }

		//only static methods
		private DataProviderFactory() {}

		internal static DataProvider GetDataProvider(string dataProviderName)
		{
			DataProvider dp = _dataProviders[dataProviderName];

			if(dp == null) 
			{
				//TODO: specialize the exception, perhaps DataSourceNotFoundException
				throw new DataAccessException("DataProvider with name '" + dataProviderName + "' not found.");
			}

			return dp;
		}
	}
}
